package distributedRE;


import java.util.ArrayList;

import java.util.List;

import clock.Clock;

import policy.stop.StopPolicy;
import informationExtractionSystem.InformationExtractor;
import querydatabaseselection.selector.QueryDatabaseSelector;
import querydatabaseselection.pair.QueryDatabasePair;
import rankingGenerator.RankingGenerator;
import searchEngine.SearchEngine;
import statistics.generator.StatisticsGenerator;
import configuration.ConfigurationLoader;
import configuration.options.AdjustmentOption;
import configuration.parameters.ArrayListParameter;

import configuration.parameters.Parameter;
import database.mediator.MediatorInterface;
import distributedRE.model.DataBase;
import distributedRE.model.DistributedREModel;
import distributedRE.model.Document;
import distributedRE.model.Query;
import distributedRE.model.QueryDatabase;
import distributedRE.model.Session;
import distributedRE.model.Tuple;

public class ExecutionPerformer {

	private static final int PROCESSED_TO_GC = 50;
	private ConfigurationLoader cl;

	public ExecutionPerformer(ConfigurationLoader cl) {
		this.cl = cl;
	}

	public void run() {
		
		//Load
		
		MediatorInterface mI = cl.getStorageMediator();
		mI.openConnection();
		
		Integer id_experiment = cl.getExperimentId();
		QueryDatabaseSelector qDbS = cl.getQueryDatabaseSelector();
		SearchEngine sE = cl.getSearchEngine();
		InformationExtractor iE = cl.getInformationExtractor();
		StopPolicy sP = cl.getStopPolicy();
		AdjustmentOption Ao = cl.getAdjustmentOption();
		StatisticsGenerator sG = cl.getStatisticsGenerator();
		DistributedREModel dREModel = new DistributedREModel();
		
		ArrayList<DataBase> dbs = cl.getDatabases();
		
		dREModel.setInformationExtractor(iE);
		
		dREModel.setDatabases(dbs);
		
		//Should be something that reads this combination.
		
		RankingGenerator rG = cl.getRankingGenerator();
		
		//Run
		
		initializeModel(dREModel);
		
		cl.getRelation().getId();
		Clock.getDateTime();
		cl.getEnvironment();
		qDbS.toString();
		sE.toString();
		sP.toString();
		Ao.toString();
		rG.toString();
		
		mI.writeExperiment(id_experiment,cl.getRelation().getId(),Clock.getDateTime(), cl.getEnvironment(), qDbS.toString(), sE.toString(),sP.toString(),Ao.toString(),rG.toString());
		
		mI.writeUsedIE(id_experiment,iE.getId());
		
		dREModel.setRelation(cl.getRelation());
		
		while (!sP.finished(dREModel)){
		
			Session s = new Session(id_experiment);
			
			//Step 1: Select M pairs [q,db]
			
			Clock.startTime();
			
			List<QueryDatabasePair> selection = qDbS.getNextSelection(dREModel, rG, s);
					
			Clock.stopTime();
			
			System.out.println("GOT NEXT SELECTION!");
			
			mI.writeSession(s.getExperiment_id(),s.getId(),Clock.getMeasuredTime());
			
			int position = 1;
			
			for (QueryDatabasePair qDP : selection) {
				
				mI.writeSessionResult(s.getExperiment_id(),s.getId(), qDP.getQuery().getId(),qDP.getDatabase().getId(),position,qDP.getScore());
			
				position++;
			}
			
			//Step 2: Perform Queries.
			
			//TODO CHECK ADDED BY ME!
			
			if (selection.size()==0){
				break;
			}
			
			for (QueryDatabasePair qDP : selection) {
				
				QueryDatabase qdb = QueryDatabase.getInstance(qDP.getQuery(),qDP.getDatabase(),dREModel);
				
				qdb.setSelectable(false);
				
				Clock.startTime();
				
				ArrayList<Document> res = (ArrayList<Document>)qDP.getDatabase().getSearcher().queryAllAllowed(qDP.getQuery(),mI);
				
				Clock.stopTime();
				
				System.out.println("SEARCH!");
				
				mI.writeQuerySession(qDP.getQuery().getId(),qDP.getDatabase().getId(),s.getExperiment_id(),s.getId(),Clock.getMeasuredTime());
				
				position = 1; 
				
				for (Document document : res) {
					
					mI.writeQueryResults(qDP.getQuery().getId(),qDP.getDatabase().getId(),s.getExperiment_id(),s.getId(),document.getId(),position,0/*sE.getScore(qDP.getQuery(),document)*/);
					
					position++;
				
				}
				
				s.add(res,qdb);
				
				qdb.addParameter(Parameter.DOCUMENTS, new ArrayListParameter<Document>(res));
				
			}
			
			//Step 3: Execute IE over documents retrieved.
			
			int times = 0;
			
			for (ArrayList<Document> documents : s.getDocumentLists()) {
				
				for (Document document : documents) {
					
					times++;
					
					Clock.startTime();
					
					ArrayList<Tuple> results = iE.process(cl.getRelation(),document);
				
					Clock.stopTime();

					System.out.println("GOT TUPLES!");
					
					if (times > PROCESSED_TO_GC){
						times = 0;
						System.out.println("Cleanning Garbage");
						Runtime r = Runtime.getRuntime();
						r.gc();
					}
					
					if (!mI.hasDocumentBeenProcessed(document.getId(),iE.getId())){
					
						mI.writeIEonDocument(iE.getId(),document.getId(),(iE.isUseful(cl.getRelation(),document)==InformationExtractor.USEFUL_VALUE),Clock.getMeasuredTime());
						
						for (Tuple tuple : results) {
							
							document.addExtractedTuple(dREModel.getInformationExtractor().getId(),dREModel.getRelation().getId(),tuple);
							
							mI.writeTuple(new Long(iE.getId()).longValue(),tuple.getRelationId(),tuple.getId(),tuple.getText(), tuple.getRootInformationExtractorId(),tuple.getRootRelationId(),tuple.getRootId());
							
							mI.writeDocumentTuple(document.getId(),iE.getId(),tuple.getRelationId(),tuple.getId());
	
						}
					} else {
						
						System.out.println("Already Processed document: " + document.getId() + " by: " + iE.getId());
						
					}
				}				
				
			}
			
			dREModel.append(s);			
			
			//Step 4: If #tuples < T
			
//			if (!sP.finished(dREModel)){
				
				//Step 4.1: Adapt parameters
				
			Ao.adaptReporter(dREModel,s,mI);
				
			System.out.println("ADAPTED!");
			
				//Step 4.2: Return to 1.
				
//			}
			
		}
		//Step 5: Finish.
		
		mI.writeEndDate(id_experiment,Clock.getDateTime());
		
		sG.printStatistics(dREModel);
	
		mI.closeConnection();
		
	}

	private void initializeModel(DistributedREModel dREModel) {
		//Will store all the precision values generated first.
		
		for (DataBase dataBase : dREModel.getDatabases()) {
			
			ArrayList<Query> queries = cl.getQueries();
			
			for (Query query : queries) {
				
				double ip = cl.getStorageMediator().getInitialPrecision(query.getId(), dataBase.getId());
				
				dREModel.setQueryForDataBase(query,dataBase,ip);
				
			}
			
		}
		
			
	}

}
